import re

import pyglet
from pyglet.gl import *
from pyglet.window import key

import camera

tile_images = {0: pyglet.resource.image("testtile.png"),
               1: pyglet.resource.image("grasstile.png"),
               2: pyglet.resource.image("concretetile.png"),
               }
tile_overlay_images = {0: pyglet.resource.image("grass_overlap_bottom.png"),
                       }
tile_width = 100
tile_height = 50

def isometric_map_coord_to_world_coord(isox, isoy):
    x = (isoy%2)*tile_width/2.0 + isox*tile_width
    y = isoy*tile_height/2.0
    
    return x, y

class Array2D(object):
    def __init__(self):
        self.__arr_dict = {}
        self.ymin, self.xmin = 0, 0
        self.ymax, self.xmax = -1, -1

    def set(self, x, y, val):
        self.xmin, self.xmax = min(x, self.xmin), max(x, self.xmax)
        self.ymin, self.ymax = min(y, self.ymin), max(y, self.ymax)
        self.__arr_dict[(x, y)] = val

    def get(self, x, y):
        return self.__arr_dict[(x, y)]

    def fill_empty(self, val):
        for x in xrange(self.xmin, self.xmax + 1):
            for y in xrange(self.ymin, self.ymax + 1):
                if (x, y) not in self.__arr_dict:
                    self.__arr_dict[(x, y)] = val

    def height(self):
        return self.ymax - self.ymin + 1

    def width(self):
        return self.xmax - self.xmin + 1

class IsometricTile(object):
    def __init__(self, type, attr):
        """Set up an isometric tile, with map coordinates x, y.
        Since this is an isometric map, the map coordinates are
        not proportional to the coordinates in which they will be
        positioned when drawn. To get this value, the position
        property is provided.
        The tile type is defined by type and specific attributes
        by attr."""

        self.type = type
        self.attr = attr

class Map(object):
    def __init__(self, filename):
        if filename is not None:
            self.parse_map(filename)
        self.draw_batch = pyglet.graphics.Batch()
        self.reindex()

    def reindex(self):
        """Create sprites for every tile and add them to the batch.
        Note that this operation might be slow, so avoid using this unless you
        can."""
        #if self.tile_layer.height()*self.tile_layer.width() == 0: return

        self.sprites = []
        for x in xrange(self.tile_layer.xmin, self.tile_layer.xmax + 1):
            for y in xrange(self.tile_layer.ymin, self.tile_layer.ymax + 1):
                xx, yy = isometric_map_coord_to_world_coord(x, y)
                s = pyglet.sprite.Sprite(tile_images[self.tile_layer.get(x, y).type],
                                         x=xx,
                                         y=yy,
                                         batch=self.draw_batch)
                self.sprites.append(s)

    def draw(self, cam, width, height):
        glPushMatrix()
        cam.focus(width, height)
        self.draw_batch.draw()
        glPopMatrix()

    def parse_map(self, filename):
        f = pyglet.resource.file(filename)
        cont = f.read()
        f.close()

        map = re.search("(?s)MAP\nVER (.*?)\nNAME (.*?)\nWIDTH (.*?)\nHEIGHT (.*?)\nFOCUS TILE (.*?) (.*?)\n.*?BEGIN TILES\n(.*?)\nEND TILES\n\nBEGIN ENEMIES\n(.*?)\nEND ENEMIES\n\nBEGIN BUILDINGS\n(.*?)\nEND BUILDINGS", cont)

        if map is None:
            raise IOError("Invalid file format")

        ver = float(map.group(1))  # Version of parser
        self.name = map.group(2)        # Name of the map to display
        self.width = int(map.group(3))  # Width of map in tiles
        self.height = int(map.group(4)) # Height of map in tiles
        self.focus_tile = isometric_map_coord_to_world_coord(int(map.group(5)), 
                                                             int(map.group(6)))
                                   # What tile to focus the camera on
        tile_string = map.group(7) # The tile data
        structure_exec_string = map.group(8)        # Placable structure data
        enemy_exec_string = map.group(9)            # The enemy data

        tiles = re.findall("(?s).*?TILE\nTYPE (.*?)\nATTRIBUTES (.*?)\n", tile_string)

        if len(tiles) is not self.width*self.height:
            raise IOError("Invalid map size or missing tile data")
        
        # Clear already existing contents
        self.tile_layer = Array2D()
    
        for y in xrange(self.height):
            for x in xrange(self.width):
                tile = tiles[y * self.width + x]
                self.tile_layer.set(x, y, IsometricTile(int(tile[0]), int(tile[1])))

        # Now for a hack. This is ugly, but necessary, because 
        # my and Jakob's code isn't really compatible, and we don't
        # have time to fix it.
        exec(structure_exec_string)
        exec(enemy_exec_string)

def test():
    win_width = 1024
    win_height = 786
    
    window = pyglet.window.Window(win_width, win_height)

    map = Map("../data/maps/testmap.map")

    glClearColor(0, 1, 0, 0)
    glEnable(GL_BLEND)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

    cam = camera.Camera((0,0),scale=20)

    @window.event
    def on_draw():
        window.clear()
        cam.focus(window.width, window.height)
        map.draw(window, cam)

    @window.event
    def on_key_press(sym, mod):
        if sym == key.UP:
            cam.y += cam.scale
        if sym == key.DOWN:
            cam.y -= cam.scale
        if sym == key.LEFT:
            cam.x -= cam.scale
        if sym == key.RIGHT:
            cam.x += cam.scale
        if sym == key.MINUS:
            cam.scale *= 2
        if sym == key.PLUS:
            cam.scale /= 2.0
    pyglet.app.run()
    

if __name__ == "__main__":
    test()
    
